/*
* @file stt.cpp
* @brief  This file contains the implementation of the STT class
* @author feiniao
* @version 1.0
* @date 2025-5-11
* @note This code is designed to work with ESP32-S3 and uses the ESP-IDF 5.2.3 framework.
*       It provides a speech-to-text (STT) functionality using Baidu's STT API.
*/


#include "stt.h"

// embeded certificate
extern const char my_stt_cert_pem_start[] asm("_binary_my_stt_cert_pem_start");
extern const char my_stt_cert_pem_end[] asm("_binary_my_stt_cert_pem_end");

char* token = NULL;     // to store the access token


/*
* @brief Memory allocation function for cJSON
* @param sz Size of memory to allocate
* @return Pointer to allocated memory
* @note This function uses heap_caps_malloc to allocate memory from PSRAM.
*/
static void* cjson_malloc(size_t sz) {
    return heap_caps_malloc(sz, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
}


/*
* @brief STT constructor
* It also checks the availability of PSRAM and initializes the recorder.
* The constructor is called when the STT object is created.
* @note The constructor initializes the cJSON library with custom memory allocation functions.
*/
STT::STT() : recorder() {
    ESP_LOGI(TAG, "STT对象创建中...");
    // 初始化内存分配器
    cJSON_Hooks hooks = {
        .malloc_fn = cjson_malloc,
        .free_fn = heap_caps_free
    };
    cJSON_InitHooks(&hooks);

    ESP_LOGI("MEM", "初始空闲堆内存: %lu", esp_get_free_heap_size());
    
    // 检查PSRAM
    if (esp_psram_is_initialized()) {
        ESP_LOGI("PSRAM", "可用: %d MB", esp_psram_get_size() / (1024 * 1024));
    } else {
        ESP_LOGW("PSRAM", "PSRAM不可用，仅使用内部RAM");
    }
    ESP_LOGI(TAG, "STT对象创建成功!");
}


/*
* @brief STT destructor
* It is called when the STT object is destroyed.
*/
STT::~STT() {
    ESP_LOGI(TAG, "STT对象销毁成功!");
}


/*
* @brief HTTP event handler for STT
* @param evt Pointer to the HTTP event structure
* @return ESP_OK on success
* @note This function handles various HTTP events such as data reception, connection finish, and disconnection.
* It accumulates the received data into a static buffer and processes it when the response is complete.
* It also parses the JSON response and extracts the recognition result or error message.
* The function uses a static buffer to store the response data and a static variable to track the current length of the data.
* The buffer is reset after processing the response.
* The function is called by the HTTP client when an event occurs.
*/
static esp_err_t stt_event_handler(esp_http_client_event_t *evt) {
    static char response_buffer[MAX_RESPONSE_LEN];  // 静态缓冲区存储响应
    static size_t response_len = 0;                 // 当前数据长度

    switch (evt->event_id) {
        case HTTP_EVENT_ON_DATA: {
            // 累积数据到缓冲区
            if (response_len < MAX_RESPONSE_LEN - 1) {
                size_t to_copy = MIN(evt->data_len, MAX_RESPONSE_LEN - response_len - 1);
                memcpy(response_buffer + response_len, evt->data, to_copy);
                response_len += to_copy;
            }
            break;
        }
        case HTTP_EVENT_ON_FINISH: {
            ESP_LOGI(TAG, "Response: %s", response_buffer);
            // 确保字符串以结束符结尾
            response_buffer[response_len] = '\0';
            
            // 解析 JSON 响应
            cJSON *response_json = cJSON_Parse(response_buffer);
            if (response_json) {
                cJSON *result = cJSON_GetObjectItem(response_json, "result");
                if (result && cJSON_IsArray(result)) {
                    cJSON *first_result = cJSON_GetArrayItem(result, 0);
                    if (cJSON_IsString(first_result)) {
                        ESP_LOGI(TAG, "识别结果: %s", first_result->valuestring);
                        char* text_copy = strdup(first_result->valuestring); // 复制字符串
                        if (xQueueSend(stt_output_queue, &text_copy, pdMS_TO_TICKS(100)) != pdTRUE) {
                            free(text_copy); // 发送失败时释放
                            ESP_LOGE(TAG, "Queue full");
                        }
                    }
                } else {
                    cJSON *error = cJSON_GetObjectItem(response_json, "err_msg");
                    ESP_LOGE(TAG, "识别失败: %s", error ? error->valuestring : "Unknown error");
                }
                cJSON_Delete(response_json);
            } else {
                ESP_LOGE(TAG, "JSON解析失败");
            }
            // 重置缓冲区
            response_len = 0;
            break;
        }
        case HTTP_EVENT_DISCONNECTED:
            response_len = 0;  // 重置以应对重连
            break;
        default:
            break;
    }
    return ESP_OK;
}


/*
* @brief HTTP event handler for fetching token
* @param evt Pointer to the HTTP event structure
* @return ESP_OK on success
* @note This function handles various HTTP events such as data reception, connection finish, and disconnection.
* It accumulates the received data into a static buffer and processes it when the response is complete.
* It also parses the JSON response and extracts the access token.
* The function uses a static buffer to store the response data and a static variable to track the current length of the data.
* The buffer is reset after processing the response.
* The function is called by the HTTP client when an event occurs.
* The function is used to fetch the access token from Baidu's API.
*/
static esp_err_t fetch_token_event_handler(esp_http_client_event_t* evt) {
    static char response_buffer[MAX_RESPONSE_LEN];  // 静态缓冲区
    static size_t response_len = 0;

    switch (evt->event_id) {
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGI(TAG, "HTTP连接成功");
            break;
        case HTTP_EVENT_ON_DATA:
            if (response_len < MAX_RESPONSE_LEN - 1) {
                size_t to_copy = MIN(evt->data_len, MAX_RESPONSE_LEN - response_len - 1);
                memcpy(response_buffer + response_len, evt->data, to_copy);
                response_len += to_copy;
            }
            break;
        case HTTP_EVENT_ON_FINISH: {
            // 确保字符串以结束符结尾
            response_buffer[response_len] = '\0';
            // 解析JSON
            cJSON *root = cJSON_Parse(response_buffer);
            if (root) {
                cJSON *refresh_token = cJSON_GetObjectItem(root, "access_token");
                // refresh_token
                if (cJSON_IsString(refresh_token)) {
                    ESP_LOGI(TAG, "access_token: %s", refresh_token->valuestring);
                    // 释放之前的 token
                    if (token) free(token);
                    // 更新 token
                    token = strdup(refresh_token->valuestring);
                    // printf("token: %s \r\n", baidu_aip_token);
                } else {
                    ESP_LOGE(TAG, "access_token not found");
                }
                cJSON_Delete(root);
            } else {
                ESP_LOGE(TAG, "JSON解析失败");
            }
            // 重置缓冲区
            response_len = 0;
            break;
        }
        case HTTP_EVENT_HEADERS_SENT:
            ESP_LOGD(TAG, "Headers sent");
            break;
        default:
            break;
    }
    return ESP_OK;
}


/*
* @brief Fetches the access token from Baidu's API
* @note This function sends an HTTP POST request to Baidu's API to fetch the access token.
* It uses the esp_http_client library to handle the HTTP request and response.
* The function configures the HTTP client with the necessary parameters such as URL, method, and event handler.
*/
void STT::fetch_token_stt() {
    ESP_LOGI(TAG, "开始获取token...");

    esp_http_client_config_t config = {
        .url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=" STT_API_KEY "&client_secret=" STT_SECRET_KEY,
        .cert_pem = my_stt_cert_pem_start,
        .method = HTTP_METHOD_POST,
        .timeout_ms = 10000,
        .event_handler = fetch_token_event_handler,
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);

    esp_http_client_set_header(client, "Content-Type", "application/x-www-form-urlencoded");
    esp_http_client_set_header(client, "Content-Length", "0");
    esp_err_t err = esp_http_client_perform(client);

    if (err == ESP_OK) {
        int status_code = esp_http_client_get_status_code(client);
        ESP_LOGI(TAG, "status_code: %d", status_code);
    } else {
        ESP_LOGE(TAG, "HTTP请求失败: %s", esp_err_to_name(err));
    }

    // 确保每次请求后释放资源
    esp_http_client_cleanup(client);
}


/*
* @brief Processes the STT (Speech-to-Text) functionality
* @note This function records audio, encodes it to base64, and sends it to Baidu's STT API.
* It handles the entire process of recording, encoding, and sending the audio data.
* The function also handles the response from the API and extracts the recognition result.
*/
void STT::process_int32t() {

    ESP_LOGI(TAG, "请说话（5以内）：");

    size_t sample_num = recorder.int32t_record(5); // x秒录音
    int32_t* buffer = (int32_t*)heap_caps_malloc(sample_num * sizeof(int32_t), 
                        MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);   // 在PSRAM中分配内存
    if (!buffer) {
        ESP_LOGE(TAG, "读取音频文件内存分配失败!");
        return;
    }
    ESP_LOGI(TAG, "读取音频文件内存分配成功!");

    recorder.read_pcm_file_int32t(buffer); // 读取PCM文件

    // 转换为base64编码
    // 计算base64编码所需长度
    size_t base64_len = 0;
    int ret = mbedtls_base64_encode(NULL, 0, &base64_len, (const unsigned char*)buffer, sample_num * sizeof(int32_t));
    if (ret != 0 && ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
        ESP_LOGE(TAG, "Base64长度计算失败: 错误码 %d", ret);
        return;
    }
    ESP_LOGI(TAG, "Base64编码长度: %d", base64_len);


    // 分配内存以存储base64编码
    unsigned char* base64_buffer = (unsigned char*)heap_caps_malloc(base64_len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    if (!base64_buffer) {
        ESP_LOGE(TAG, "Base64内存分配失败!");
        heap_caps_free(buffer);
        return;
    }
    ESP_LOGI(TAG, "Base64内存分配成功!");


    // 执行base64编码
    ret = mbedtls_base64_encode(base64_buffer, base64_len, &base64_len, (const unsigned char*)buffer, sample_num * sizeof(int32_t));
    if (ret != 0) {
        ESP_LOGE(TAG, "Base64编码失败: 错误码 %d", ret);
        heap_caps_free(buffer);
        heap_caps_free(base64_buffer);
        return;
    }

    // 添加终止符
    base64_buffer[base64_len] = '\0';  // 添加终止符
    // 打印base64编码长度
    ESP_LOGI(TAG, "Base64编码长度: %d", base64_len + 1);   // 包含终止符
    // 打印前32字节（安全方式）
    ESP_LOGI(TAG, "base64_buffer前64字节: %.*s", 64, (char*)base64_buffer);

    // 转换为JSON格式
    cJSON* json = cJSON_CreateObject();
    if (!json) {
        ESP_LOGE(TAG, "创建JSON对象失败!");
        heap_caps_free(base64_buffer);
        return;
    }

    fetch_token_stt(); // 获取token
   

    cJSON_AddStringToObject(json, "dev_pid", "1537"); // 普通话输入模型
    cJSON_AddStringToObject(json, "format", "pcm");
    cJSON_AddNumberToObject(json, "rate", SAMPLE_RATE);
    cJSON_AddNumberToObject(json, "channel", 1);
    cJSON_AddStringToObject(json, "token", token); // 使用 token
    cJSON_AddStringToObject(json, "cuid", CUID);
    cJSON_AddNumberToObject(json, "len", sample_num * sizeof(int32_t));    // pcm文件的字节数
    if (cJSON_AddStringToObject(json, "speech", (const char*)base64_buffer) == NULL) {
        ESP_LOGE(TAG, "添加音频数据失败!");
        cJSON_Delete(json);
        heap_caps_free(base64_buffer);
        return;
    }
    char* json_str = cJSON_PrintUnformatted(json);
    if (!json_str) {
        ESP_LOGE(TAG, "生成JSON字符串失败!");
    } else {
        ESP_LOGI(TAG, "生成的JSON字符串(前200): %.200s", json_str);
    }


    // // 配置 HTTP 客户端
    // 严格按照结构体定义顺序初始化
    esp_http_client_config_t config = {
        .url = API_URL_STT,
        .cert_pem = my_stt_cert_pem_start,
        .method = HTTP_METHOD_POST,
        .timeout_ms = 10000,
        .event_handler = stt_event_handler,
    };

    // 设置HTTP客户端
    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_http_client_set_header(client, "Accept", "application/json");

    // 设置post数据
    esp_http_client_set_post_field(client, json_str, strlen(json_str));

    // 发送请求
    esp_err_t err = esp_http_client_perform(client);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "HTTP 请求失败: %s", esp_err_to_name(err));
    }
    // 检查响应码
    int status_code = esp_http_client_get_status_code(client);
    if (status_code != 200) {
        ESP_LOGE(TAG, "HTTP Error: %d", status_code);
    }

    
    cJSON_free(json_str); // 释放JSON字符串内存
    cJSON_Delete(json); // 删除JSON对象
    heap_caps_free(buffer); // 释放PCM数据内存
    heap_caps_free(base64_buffer); // 释放base64编码内存
    esp_http_client_cleanup(client);    // 清理HTTP客户端

    // 打印内存状态
    ESP_LOGI(TAG, "当前空闲堆内存: %lu", esp_get_free_heap_size());
    ESP_LOGI(TAG, "最大连续内存块: %d", heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM));
    ESP_LOGI(TAG, "STT处理完成!");
    ESP_LOGI(TAG, "请说话按下按键说话...");
}


/*
* @brief Processes the STT (Speech-to-Text) functionality
* @note This function records audio, encodes it to base64, and sends it to Baidu's STT API.
* It handles the entire process of recording, encoding, and sending the audio data.
* The function also handles the response from the API and extracts the recognition result.
*/
void STT::process_int16t() {

    ESP_LOGI(TAG, "请说话（5以内）：");

    size_t sample_num = recorder.int16t_record(5); // x秒录音
    int16_t* buffer = (int16_t*)heap_caps_malloc(sample_num * sizeof(int16_t), 
                        MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);   // 在PSRAM中分配内存
    if (!buffer) {
        ESP_LOGE(TAG, "读取音频文件内存分配失败!");
        return;
    }
    ESP_LOGI(TAG, "读取音频文件内存分配成功!");

    recorder.read_pcm_file_int16t(buffer); // 读取PCM文件

    // 转换为base64编码

    // 计算base64编码所需长度
    size_t base64_len = 0;
    int ret = mbedtls_base64_encode(NULL, 0, &base64_len, (const unsigned char*)buffer, sample_num * sizeof(int16_t));
    if (ret != 0 && ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
        ESP_LOGE(TAG, "Base64长度计算失败: 错误码 %d", ret);
        return;
    }
    ESP_LOGI(TAG, "Base64编码长度: %d", base64_len);


    // 分配内存以存储base64编码
    unsigned char* base64_buffer = (unsigned char*)heap_caps_malloc(base64_len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    if (!base64_buffer) {
        ESP_LOGE(TAG, "Base64内存分配失败!");
        heap_caps_free(buffer);
        return;
    }
    ESP_LOGI(TAG, "Base64内存分配成功!");


    // 执行base64编码
    ret = mbedtls_base64_encode(base64_buffer, base64_len, &base64_len, (const unsigned char*)buffer, sample_num * sizeof(int16_t));
    if (ret != 0) {
        ESP_LOGE(TAG, "Base64编码失败: 错误码 %d", ret);
        heap_caps_free(buffer);
        heap_caps_free(base64_buffer);
        return;
    }

    // 添加终止符
    base64_buffer[base64_len] = '\0';  // 添加终止符
    // 打印base64编码长度
    ESP_LOGI(TAG, "Base64编码长度: %d", base64_len + 1);   // 包含终止符
    // 打印前32字节（安全方式）
    ESP_LOGI(TAG, "base64_buffer前64字节: %.*s", 64, (char*)base64_buffer);

    // JSON请求构造
    cJSON* json = cJSON_CreateObject();
    if (!json) {
        ESP_LOGE(TAG, "创建JSON对象失败!");
        heap_caps_free(base64_buffer);
        return;
    }

    fetch_token_stt(); // 获取token

    cJSON_AddStringToObject(json, "dev_pid", "1537"); // 普通话输入模型
    cJSON_AddStringToObject(json, "format", "pcm");
    cJSON_AddNumberToObject(json, "rate", SAMPLE_RATE);
    cJSON_AddNumberToObject(json, "channel", 1);
    cJSON_AddStringToObject(json, "token", token); // 使用 token
    cJSON_AddStringToObject(json, "cuid", CUID);
    cJSON_AddNumberToObject(json, "len", sample_num * sizeof(int16_t));    // pcm文件的字节数
    if (cJSON_AddStringToObject(json, "speech", (const char*)base64_buffer) == NULL) {
        ESP_LOGE(TAG, "添加音频数据失败!");
        cJSON_Delete(json);
        heap_caps_free(base64_buffer);
        return;
    }
    char* json_str = cJSON_PrintUnformatted(json);
    if (!json_str) {
        ESP_LOGE(TAG, "生成JSON字符串失败!");
    } else {
        ESP_LOGI(TAG, "生成的JSON字符串(前200): %.200s", json_str);
    }


    // // 配置 HTTP 客户端
    // 严格按照结构体定义顺序初始化
    esp_http_client_config_t config = {
        .url = API_URL_STT,
        .cert_pem = my_stt_cert_pem_start,
        .method = HTTP_METHOD_POST,
        .timeout_ms = 100000,
        .event_handler = stt_event_handler,
    };

    // 设置HTTP客户端
    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_http_client_set_header(client, "Accept", "application/json");

    // 设置post数据
    esp_http_client_set_post_field(client, json_str, strlen(json_str));

    // 发送请求
    esp_err_t err = esp_http_client_perform(client);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "HTTP 请求失败: %s", esp_err_to_name(err));
    }
    // 检查响应码
    int status_code = esp_http_client_get_status_code(client);
    if (status_code != 200) {
        ESP_LOGE(TAG, "HTTP Error: %d", status_code);
    }

    
    cJSON_free(json_str); // 释放JSON字符串内存
    cJSON_Delete(json); // 删除JSON对象
    heap_caps_free(buffer); // 释放PCM数据内存
    heap_caps_free(base64_buffer); // 释放base64编码内存
    esp_http_client_cleanup(client);    // 清理HTTP客户端
    recorder.clear_recorder_File();

    // 打印内存状态
    ESP_LOGI(TAG, "当前空闲堆内存: %lu", esp_get_free_heap_size());
    ESP_LOGI(TAG, "最大连续内存块: %d", heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM));
    ESP_LOGI(TAG, "STT处理完成!");
}
